﻿//--------------------------------------------------------------------------
// 
//  Copyright (c) Chili Software.  All rights reserved. 
// 
//  File: LinqExtensions.cs
//
//  Description: Holds extension methods for LINQ in Opf3.
// 
//--------------------------------------------------------------------------

using System;
using Chili.Opf3.Mapping;
using Chili.Opf3.Linq;
using System.Reflection;
using System.Linq.Expressions;
using OrderDirection = Chili.Opf3.Query.SortDirections;
using CompiledQuery = Chili.Opf3.Query.CompiledQuery;

namespace Chili.Opf3
{
	/// <summary>
	/// This class holds extension methods for LINQ in Opf3.
	/// </summary>
	public static class LinqExtensions
	{
		private static bool _providerSet = false;
		private static readonly object LOCK = new object();

		/// <summary>
		/// Gets all persistents from the storage.
		/// </summary>
		public static LinqQuery<T> GetPersistents<T>(this ObjectContext context)
		{
			if (context == null)
				throw new ArgumentNullException("context");

			// Make sure the anonymous type mapping provider is set.
			if (!_providerSet)
			{
				lock (LOCK)
				{
					if (!_providerSet)
					{
						TypeMapping.Providers.Add(new AnonymousTypeMappingProvider());
						_providerSet = true;
					}
				}
			}

			return new LinqQuery<T>(context);
		}

		/// <summary>
		/// Removes all persistents from the storage that match the given query.
		/// </summary>
		/// <param name="context">Represents the ObjectContext instance that is used to remove all persistent objects.</param>
		/// <param name="query">The query that is executed to find the persistent objects that are deleted.</param>
		public static void RemoveAll<T>(this ObjectContext context, LinqQuery<T> query)
		{
			if (context == null)
				throw new ArgumentNullException("context");

			CompiledQuery compiledquery = LinqQueryCommandBuilder.ToCompiledDeleteQuery(context.Storage, query);
			context.Storage.ExecuteCommand(compiledquery);
		}

		/// <summary>
		/// Invoked each time a where condition is specified.
		/// </summary>
		/// <param name="predicate">The predicate used to filter the query.</param>
		/// <param name="source">The LinqQuery that contains the LINQ statement until to the call of this method.</param>
		public static LinqQuery<TSource> Where<TSource>(this LinqQuery<TSource> source, Expression<Func<TSource, bool>> predicate)
		{
			if (source == null)
				throw new ArgumentNullException("source");
			if (predicate == null)
				throw new ArgumentNullException("predicate");

			// clone the original query first.
			var clone = source.Clone<TSource>();

			clone.Expressions.Where.Add(
				Expression.Call(((MethodInfo)MethodBase.GetCurrentMethod()).MakeGenericMethod(typeof(TSource)), 
				Expression.Constant(source), Expression.Quote(predicate)));
			return clone;
		}

		/// <summary>
		/// Invoked each time an order by condition is specified.
		/// </summary>
		/// <param name="keySelector">The selector used to specify the order by.</param>
		/// <param name="source">The LinqQuery that contains the LINQ statement until to the call of this method.</param>
		public static LinqQuery<TSource> OrderBy<TSource, TKey>(this LinqQuery<TSource> source, Expression<Func<TSource, TKey>> keySelector)
		{
			if (source == null)
				throw new ArgumentNullException("source");
			if (keySelector == null)
				throw new ArgumentNullException("keySelector");

			// clone the original query first.
			var clone = source.Clone<TSource>();

			clone.Expressions.OrderBy.Add(
				new OrderByExpression(Expression.Call(((MethodInfo)MethodBase.GetCurrentMethod()).MakeGenericMethod(typeof(TSource), typeof(TKey)),
				Expression.Constant(source), Expression.Quote(keySelector)), OrderDirection.Ascending));
			return clone;
		}

		/// <summary>
		/// Invoked each time an order by descending condition is specified.
		/// </summary>
		/// <param name="keySelector">The selector used to specify the order by.</param>
		/// <param name="source">The LinqQuery that contains the LINQ statement until to the call of this method.</param>
		public static LinqQuery<TSource> OrderByDescending<TSource, TKey>(this LinqQuery<TSource> source, Expression<Func<TSource, TKey>> keySelector)
		{
			if (source == null)
				throw new ArgumentNullException("source");
			if (keySelector == null)
				throw new ArgumentNullException("keySelector");

			// clone the original query first.
			var clone = source.Clone<TSource>();

			clone.Expressions.OrderBy.Add(
				new OrderByExpression(Expression.Call(((MethodInfo)MethodBase.GetCurrentMethod()).MakeGenericMethod(typeof(TSource), typeof(TKey)),
				Expression.Constant(source), Expression.Quote(keySelector)), OrderDirection.Descending));
			return clone;
		}

		/// <summary>
		/// Invoked each time a second order by ascending condition is specified.
		/// </summary>
		/// <param name="keySelector">The selector used to specify the order by.</param>
		/// <param name="source">The LinqQuery that contains the LINQ statement until to the call of this method.</param>
		public static LinqQuery<TSource> ThenBy<TSource, TKey>(this LinqQuery<TSource> source, Expression<Func<TSource, TKey>> keySelector)
		{
			if (source == null)
				throw new ArgumentNullException("source");
			if (keySelector == null)
				throw new ArgumentNullException("keySelector");

			// clone the original query first.
			var clone = source.Clone<TSource>();

			clone.Expressions.OrderBy.Add(
				new OrderByExpression(Expression.Call(((MethodInfo)MethodBase.GetCurrentMethod()).MakeGenericMethod(typeof(TSource), typeof(TKey)),
				Expression.Constant(source), Expression.Quote(keySelector)), OrderDirection.Ascending));
			return clone;
		}

		/// <summary>
		/// Invoked each time an order by descending condition is specified.
		/// </summary>
		/// <param name="keySelector">The selector used to specify the order by.</param>
		/// <param name="source">The LinqQuery that contains the LINQ statement until to the call of this method.</param>
		public static LinqQuery<TSource> ThenByDescending<TSource, TKey>(this LinqQuery<TSource> source, Expression<Func<TSource, TKey>> keySelector)
		{
			if (source == null)
				throw new ArgumentNullException("source");
			if (keySelector == null)
				throw new ArgumentNullException("keySelector");

			// clone the original query first.
			var clone = source.Clone<TSource>();

			clone.Expressions.OrderBy.Add(
				new OrderByExpression(Expression.Call(((MethodInfo)MethodBase.GetCurrentMethod()).MakeGenericMethod(typeof(TSource), typeof(TKey)),
				Expression.Constant(source), Expression.Quote(keySelector)), OrderDirection.Descending));
			return clone;
		}

		/// <summary>
		/// Invoked each time a join is specified.
		/// </summary>
		/// <param name="outer">The outer part of the join.</param>
		/// <param name="inner">The inner part of the join.</param>
		/// <param name="outerKeySelector">The outer key.</param>
		/// <param name="innerKeySelector">The inner key.</param>
		/// <param name="resultSelector">The result of the join.</param>
		public static LinqQuery<TResult> Join<TOuter, TInner, TKey, TResult>(this LinqQuery<TOuter> outer, LinqQuery<TInner> inner, Expression<Func<TOuter, TKey>> outerKeySelector, Expression<Func<TInner, TKey>> innerKeySelector, Expression<Func<TOuter, TInner, TResult>> resultSelector)
		{
			if (outer == null)
				throw new ArgumentNullException("outer");
			if (inner == null)
				throw new ArgumentNullException("inner");
			if (outerKeySelector == null)
				throw new ArgumentNullException("outerKeySelector");
			if (innerKeySelector == null)
				throw new ArgumentNullException("innerKeySelector");
			if (resultSelector == null)
				throw new ArgumentNullException("resultSelector");

			// clone the original query first.
			var clone = outer.Clone<TResult>();

			clone.Expressions.Join.Add(
				Expression.Call(((MethodInfo)MethodBase.GetCurrentMethod()).MakeGenericMethod(typeof(TOuter), typeof(TInner), typeof(TKey), typeof(TResult)), 
				Expression.Constant(outer), Expression.Constant(inner), Expression.Quote(outerKeySelector), Expression.Quote(innerKeySelector), Expression.Quote(resultSelector)));
			return clone;
		}

		/// <summary>
		/// Invoked each time a group join is specified.
		/// </summary>
		/// <param name="outer">The outer part of the join.</param>
		/// <param name="inner">The inner part of the join.</param>
		/// <param name="outerKeySelector">The outer key.</param>
		/// <param name="innerKeySelector">The inner key.</param>
		/// <param name="resultSelector">The result of the join.</param>
		public static LinqQuery<TResult> GroupJoin<TOuter, TInner, TKey, TResult>(this LinqQuery<TOuter> outer, LinqQuery<TInner> inner, Expression<Func<TOuter, TKey>> outerKeySelector, Expression<Func<TInner, TKey>> innerKeySelector, Expression<Func<TOuter, LinqQuery<TInner>, TResult>> resultSelector)
		{
			if (outer == null)
				throw new ArgumentNullException("outer");
			if (inner == null)
				throw new ArgumentNullException("inner");
			if (outerKeySelector == null)
				throw new ArgumentNullException("outerKeySelector");
			if (innerKeySelector == null)
				throw new ArgumentNullException("innerKeySelector");
			if (resultSelector == null)
				throw new ArgumentNullException("resultSelector");

			// clone the original query first.
			var clone = outer.Clone<TResult>();

			clone.Expressions.GroupJoin.Add(
				Expression.Call(((MethodInfo)MethodBase.GetCurrentMethod()).MakeGenericMethod(typeof(TOuter), typeof(TInner), typeof(TKey), typeof(TResult)), 
				Expression.Constant(outer), Expression.Constant(inner), Expression.Quote(outerKeySelector), Expression.Quote(innerKeySelector), Expression.Quote(resultSelector)));
			return clone;
		}

		/// <summary>
		/// Invoked each time a select many is specified.
		/// </summary>
		/// <param name="collectionSelector">The selector for the collection.</param>
		/// <param name="resultSelector">The selector used for the select result.</param>
		/// <param name="source">The LinqQuery that contains the LINQ statement until to the call of this method.</param>
		public static LinqQuery<TResult> SelectMany<TSource, TCollection, TResult>(this LinqQuery<TSource> source, Expression<Func<TSource, LinqQuery<TCollection>>> collectionSelector, Expression<Func<TSource, TCollection, TResult>> resultSelector)
		{
			if (source == null)
				throw new ArgumentNullException("source");
			if (collectionSelector == null)
				throw new ArgumentNullException("collectionSelector");
			if (resultSelector == null)
				throw new ArgumentNullException("resultSelector");

			// clone the original query first.
			var clone = source.Clone<TResult>();

			clone.Expressions.SelectMany = 
				Expression.Call(((MethodInfo)MethodBase.GetCurrentMethod()).MakeGenericMethod(typeof(TSource), typeof(TCollection), typeof(TResult)),
				Expression.Constant(source), Expression.Quote(collectionSelector), Expression.Quote(resultSelector));
			return clone;
		}

		/// <summary>
		/// Invoked each time a select is specified.
		/// </summary>
		/// <param name="selector">The selector used in the select.</param>
		/// <param name="source">The LinqQuery that contains the LINQ statement until to the call of this method.</param>
		public static LinqQuery<TResult> Select<TSource, TResult>(this LinqQuery<TSource> source, Expression<Func<TSource, TResult>> selector)
		{
			if (source == null)
				throw new ArgumentNullException("source");
			if (selector == null)
				throw new ArgumentNullException("selector");

			// clone the original query first.
			var clone = source.Clone<TResult>();

			clone.Expressions.Select = 
				Expression.Call(((MethodInfo)MethodBase.GetCurrentMethod()).MakeGenericMethod(typeof(TSource), typeof(TResult)), 
				Expression.Constant(source), Expression.Quote(selector));
			return clone;
		}

		/// <summary>
		/// Invoked each time a take is specified.
		/// </summary>
		/// <param name="count">The amount of top items to return.</param>
		/// <param name="source">The LinqQuery that contains the LINQ statement until to the call of this method.</param>
		public static LinqQuery<TSource> Take<TSource>(this LinqQuery<TSource> source, int count)
		{
			// clone the original query first.
			var clone = source.Clone<TSource>();

			clone.Expressions.TopCount = count;
			return clone;
		}

		/// <summary>
		/// Gets the first element that is returned by the query. If no result is returned
		/// an exception is thrown.
		/// </summary>
		/// <param name="source">The query that is exectued.</param>
		public static TSource First<TSource>(this LinqQuery<TSource> source)
		{
			TSource obj = source.GetObject();
			if (obj == null)
				throw new InvalidOperationException("The query didn't return any results.");

			return obj;
		}

		/// <summary>
		/// Gets the first element that is returned by the query. Null is returned if no
		/// object could be found.
		/// </summary>
		/// <param name="source">The query that is exectued.</param>
		public static TSource FirstOrDefault<TSource>(this LinqQuery<TSource> source)
		{
			return source.GetObject();
		}

		/// <summary>
		/// Returns the default item on a join where the joined item might not be available.
		/// </summary>
		public static LinqQuery<TSource> DefaultIfEmpty<TSource>(this LinqQuery<TSource> source)
		{
			if (source == null)
				throw new ArgumentNullException("source");

			return source;
		}
	}
}
